
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "assembler_parser_test.h"

using assembler::Lexem;
using assembler::Lexer;
using assembler::Parser;

namespace assembler_test {

void ParserTest::Test() {
   TestLexer();
   TestParser();
}

void ParserTest::CheckLexemString(const Lexem &lexem, const char *str) {
  assert(!strncmp(lexem.lexem_str, str, lexem.length));
}

void ParserTest::TestLexer() {
  const char *lexemes = "MOV ADD,F1 @function 3.2 %#$  ";
  Lexer lexer(lexemes, strlen(lexemes));
  Lexem lexem;
  
  assert(lexer.ConsumeLexem(&lexem));
  assert(lexem.type == Lexem::COMMAND_NAME);
  CheckLexemString(lexem, "MOV");

  assert(lexer.ConsumeLexem(&lexem));
  assert(lexem.type == Lexem::COMMAND_NAME);
  CheckLexemString(lexem, "ADD");

  assert(lexer.ConsumeLexem(&lexem));
  assert(lexem.type == Lexem::COMMA);
  CheckLexemString(lexem, ",");

  assert(lexer.ConsumeLexem(&lexem));
  assert(lexem.type == Lexem::REGISTER_NAME);
  CheckLexemString(lexem, "F1");

  assert(lexer.ConsumeLexem(&lexem));
  assert(lexem.type == Lexem::FUNCTION_NAME);
  CheckLexemString(lexem, "@function");

  assert(lexer.ConsumeLexem(&lexem));
  assert(lexem.type == Lexem::CONSTANT);
  CheckLexemString(lexem, "3.2");

  assert(lexer.ConsumeLexem(&lexem));
  assert(lexem.type == Lexem::NO_TYPE);

  assert(!lexer.ConsumeLexem(&lexem));
}

void ParserTest::TestParser() {
  const char *lexemes =
    "MOV F1, 1.000\n"
    "MULT F1, F1\n"
    "ADD F1, 1.000\n"
    "MOV F0, 1.000\n"
    "DIV F0, F1\n"
    "CALL @sin\n"
    "RET\n" // everything below is invalid:
    "F1 MOV F2, 0.0000";
  Lexer lexer(lexemes, strlen(lexemes));
  Parser parser(&lexer);

  assert(parser.ConsumeCommand());
  assert(parser.cmd().type() == assembler::MOV);
  CheckRegisterArgument(parser.cmd().arg1(), 1);
  CheckConstantArgument(parser.cmd().arg2(), 1.000);

  assert(parser.ConsumeCommand());
  assert(parser.cmd().type() == assembler::MULT);
  CheckRegisterArgument(parser.cmd().arg1(), 1);
  CheckRegisterArgument(parser.cmd().arg2(), 1);

  assert(parser.ConsumeCommand());
  assert(parser.cmd().type() == assembler::ADD);
  CheckRegisterArgument(parser.cmd().arg1(), 1);
  CheckConstantArgument(parser.cmd().arg2(), 1.000);

  assert(parser.ConsumeCommand());
  assert(parser.cmd().type() == assembler::MOV);
  CheckRegisterArgument(parser.cmd().arg1(), 0);
  CheckConstantArgument(parser.cmd().arg2(), 1.000);

  assert(parser.ConsumeCommand());
  assert(parser.cmd().type() == assembler::DIV);
  CheckRegisterArgument(parser.cmd().arg1(), 0);
  CheckRegisterArgument(parser.cmd().arg2(), 1);

  assert(parser.ConsumeCommand());
  assert(parser.cmd().type() == assembler::CALL);
  CheckFunctionArgument(parser.cmd().arg1(), "sin");

  assert(parser.ConsumeCommand());
  assert(parser.cmd().type() == assembler::RET);

  // Below is only invalid string structure:
  assert(!parser.ConsumeCommand());
  assert(!parser.is_valid());
}

void ParserTest::CheckRegisterArgument(const assembler::CommandArgument &arg, int id) {
  assert(arg.type == assembler::ARGUMENT_REGISTER);
  assert(arg.arg.register_id == id);
}

void ParserTest::CheckConstantArgument(const assembler::CommandArgument &arg,
                                       double value) {
  assert(arg.type == assembler::ARGUMENT_CONSTANT);
  assert(fabs(arg.arg.constant_value - value) < 1e-5);
}

void ParserTest::CheckFunctionArgument(const assembler::CommandArgument &arg,
                                       const char *name) {
  assert(arg.type == assembler::ARGUMENT_FUNCTION_NAME);
  assert(strncmp(arg.arg.function.name, name, arg.arg.function.length) == 0);
 }

}  // namespace assembler_test